#include <mathematics/Color.h>

namespace ne
{
    const Color Color::ZERO(0.0f, 0.0f, 0.0f, 0.0f);
    const Color Color::BLACK(0.0f, 0.0f, 0.0f, 1.0f);
    const Color Color::DARK(0.25f, 0.25f, 0.25f, 1.0f);
    const Color Color::GRAY(0.5f, 0.5f, 0.5f, 1.0f);
    const Color Color::LIGHT(0.75f, 0.75f, 0.75f, 1.0f);
    const Color Color::WHITE(1.0f, 1.0f, 1.0f, 1.0f);
    const Color Color::RED(1.0f, 0.0f, 0.0f, 1.0f);
    const Color Color::GREEN(0.0f, 1.0f, 0.0f, 1.0f);
    const Color Color::BLUE(0.0f, 0.0f, 1.0f, 1.0f);
    const Color Color::YELLOW(1.0f, 1.0f, 0.0f, 1.0f);
    const Color Color::PURPLE(1.0f, 0.0f, 1.0f, 1.0f);
    const Color Color::CYAN(0.0f, 1.0f, 1.0f, 1.0f);
    const Color Color::ORANGE(1.0f, 0.5f, 0.0f, 1.0f);
    const Color Color::SKY(0.0f, 0.5f, 1.0f, 1.0f);

    void Color::setHSB(const real hue, const real saturation, const real brightness)
    {
        // wrap hue
        real hueTemp = hue;
		if (hueTemp > 1.0f)
		{
			hueTemp -= (int)hueTemp;
		}
		else if (hue < 0.0f)
		{
			hueTemp += (int)hueTemp + 1;
		}
		// clamp saturation / brightness
        real saturationTemp = saturation;
        real brightnessTemp = brightness;
		Math::Clamp(saturationTemp);
		Math::Clamp(brightnessTemp);

		if (brightnessTemp == 0.0f)
		{   
			// early exit, this has to be black
			r = g = b = 0.0f;
			return;
		}

		if (saturationTemp == 0.0f)
		{   
			// early exit, this has to be grey
			r = g = b = brightness;
			return;
		}


		real hueDomain  = hueTemp * 6.0f;
		if (hueDomain >= 6.0f)
		{
			// wrap around, and allow mathematical errors
			hueDomain = 0.0f;
		}
		uint16_t domain = (uint16_t)hueDomain;
		real f1 = brightnessTemp * (1.0f - saturationTemp);
		real f2 = brightnessTemp * (1.0f - saturationTemp * (hueDomain - domain));
		real f3 = brightnessTemp * (1.0f - saturationTemp * (1.0f - (hueDomain - domain)));

		switch (domain)
		{
		    case 0:
    			// red domain; green ascends
    			r = brightnessTemp;
    			g = f3;
    			b = f1;
    			break;

    		case 1:
    			// yellow domain; red descends
    			r = f2;
    			g = brightnessTemp;
    			b = f1;
    			break;

    		case 2:
    			// green domain; blue ascends
    			r = f1;
    			g = brightnessTemp;
    			b = f3;
    			break;

    		case 3:
    			// cyan domain; green descends
    			r = f1;
    			g = f2;
    			b = brightnessTemp;
    			break;

    		case 4:
    			// blue domain; red ascends
    			r = f3;
    			g = f1;
    			b = brightnessTemp;
    			break;

    		case 5:
    			// magenta domain; blue descends
    			r = brightnessTemp;
    			g = f1;
    			b = f2;
    			break;
		}
    }

    void Color::getHSB(real &hue, real &saturation, real &brightness) const
    {
        real vMin = std::min(r, std::min(g, b));
		real vMax = std::max(r, std::max(g, b));
		real delta = vMax - vMin;

		brightness = vMax;

        if (delta == 0.0f)
		{
			// grey
			hue = 0;
			saturation = 0;
		}
		else                                    
		{
			// a colour
			saturation = delta / vMax;

            real invDelta = 1.0f / delta;
			real deltaR = (((vMax - r) / 6.0f) + (delta * 0.5f)) * invDelta;
			real deltaG = (((vMax - g) / 6.0f) + (delta * 0.5f)) * invDelta;
			real deltaB = (((vMax - b) / 6.0f) + (delta * 0.5f)) * invDelta;

			if (Math::Equal(r, vMax))
            {
				hue = deltaB - deltaG;
            }
			else if (Math::Equal(g, vMax))
            {
				hue = 0.3333333f + deltaR - deltaB;
            }
			else if (Math::Equal(b, vMax))
            {
				hue = 0.6666667f + deltaG - deltaR;
            }

			if (hue < 0.0f)
            {
				hue += 1.0f;
            }
			if (hue > 1.0f)
            {
				hue -= 1.0f;
            }
		}
    }

    Color operator + (const real vl, const Color& vr)
    {
        return Color(vl + vr.r, vl + vr.g, vl + vr.b, vl + vr.a);
    }

    Color operator - (const real vl, const Color& vr)
    {
        return Color(vl - vr.r, vl - vr.g, vl - vr.b, vl - vr.a);
    }

    Color operator * (const real vl, const Color& vr)
    {
        return Color(vl * vr.r, vl * vr.g, vl * vr.b, vl * vr.a);
    }

    Color operator / (const real vl, const Color& vr)
    {
        assert((vr.r != 0.0f) && (vr.g != 0.0f) && (vr.b != 0.0f) && (vr.a != 0.0f));
        return Color(vl / vr.r, vl / vr.g, vl / vr.b, vl / vr.a);
    }

    std::ostream& operator << (std::ostream &o, const Color &c)
    {
        o << "Color(r=" << c.r << ", " <<
                   "g=" << c.g << ", " <<
                   "b=" << c.b << ", " <<
                   "a=" << c.a << ")\n";
        return o;
    }

    bool Color::Equal(const Color &vS, const Color &vD, const real tolerance)
    {
        return (Math::Equal(vS.r, vD.r, tolerance) &&
                Math::Equal(vS.g, vD.g, tolerance) &&
                Math::Equal(vS.b, vD.b, tolerance) &&
                Math::Equal(vS.a, vD.a, tolerance));
    }

    Color Color::Mid(const Color &vS, const Color &vD)
    {
        return (vS + vD) * 0.5f;
    }

    Color Color::Lerp(const Color &vS, const Color &vD, const real u)
    {
        return vS + (vD - vS) * u;
    }

    Color Color::Bezier(const Color &vS, const Color &vA, const Color &vB, const Color &vD, const real u)
    {
        Color a = vD - vS + vA * 3.0f - vB * 3.0f;
        Color b = vS * 3.0f - vA * 6.0f + vB * 3.0f;
        Color c = vA * 3.0f - vS * 3.0f;
        real uu = u * u;
        real uuu = uu * u;
        return (a * uuu) + (b * uu) + (c * u) + vS;
    }
}

